package com.ease.util {

   import com.ease.util.events.MethodReturnEvent;

   import flash.utils.Dictionary;
   import flash.utils.getDefinitionByName;
   import flash.utils.getQualifiedClassName;

   import mx.utils.ObjectUtil;

   [ExcludeClass]
   internal class MxmlMetadata {
      /**
       * An dictionary containing the object loading info.  The key is the id of the
       * load info and the value is the actual loading info provided by the
       * IClassLoader.getLoadInfo()
       */
      private var _objectInfoMap:Dictionary = new Dictionary();

      /**
       * An instance of the classloader
       */
      private var _classLoader:IClassLoader = null;

      /**
       * @return the instance of the classloader.
       */
      public function get classLoader():IClassLoader {
         if (_classLoader == null) {
            _classLoader = new DefaultClassLoader;
         }
         return _classLoader;
      }

      /**
       * Sets the new classloader for this metadata.
       *
       * @param cl The new classloader to set.
       */
      public function set classLoader(cl:IClassLoader):void {
         _classLoader = cl;
      }

      /**
       * Called to load the metadata from the root xml data.
       *
       * @param parent The root xml to load metadata.
       */
      public function load(parentNode:XML):void {
         var className:String = parentNode._metadata_.@loader;
         if (!className) {
            // No metadata found
            // Delete the metadata tag from the original xml
            delete parentNode._metadata_;
            return;
         }

         // Load the classloader if it is not overwritten
         try {
            var clazz:Class = getDefinitionByName(className) as Class;
            _classLoader = new clazz();
         } catch (e:Error) {
            // Delete the metadata tag from the original xml
            delete parentNode._metadata_;
            throw new ReferenceError("Failed to load classloader " + className);
         }

         var objectInfoMapNode:XML = parentNode._metadata_.children()[0];
         if (!objectInfoMapNode) {
            // No object load info defined
            // Delete the metadata tag from the original xml
            delete parentNode._metadata_;
            return;
         }

         var deserializer:MxmlDeserializer = new MxmlDeserializer();
         _objectInfoMap = deserializer.xmlToObject(objectInfoMapNode) as Dictionary;

         // Delete the metadata tag from the original xml
         delete parentNode._metadata_;
      }

      /**
       * Called to save the metadata into the give parent xml.
       *
       * @param parent The root xml under which the metadata will be saved.
       */
      public function save(parentNode:XML):void {
         if (isDefaultClassLoader()) {
            // No need to include the metadata as it uses the default classloader.
            return;
         }

         var loaderType:String = getQualifiedClassName(classLoader);

         // Including the metadata tag
         var metadata:XML = <_metadata_/>;
         metadata.@loader = loaderType;
         parentNode.appendChild(metadata);

         if (MixedUtil.isDictionaryEmpty(_objectInfoMap)) {
            // Class loading info not available for the serialized object
            return;
         }

         var serializer:MxmlSerializer = new MxmlSerializer();
         metadata.appendChild(serializer.objectToXml(_objectInfoMap));
      }

      /**
       * Called to insert or update the object load info.
       *
       * @param info The object load info.
       *
       * @return The key of the load info, which can be used to retrieve later.
       * If the load info is null or invalid, this will return -1.
       */
      public function insertOrUpdateLoadInfo(info:Object):int {
         // Lookup local cache for already assigned loadInfo
         var refId:int = 0;
         for (var id:* in _objectInfoMap) {
            var linfo:Object = _objectInfoMap[id];
            if (ObjectUtil.compare(info, linfo) == 0) {
               return id;
            }
            refId = Math.max(refId, id);
         }

         refId++; // Increament id for insert

         // Caching the object load info
         _objectInfoMap[refId] = info;
         return refId;
      }

      /**
       * Returns the load info object for the given node.
       */
      public function getLoadInfo(refId:String):Object {
         if (refId != null) {
            return _objectInfoMap[refId];
         }
         return null;
      }

      /**
       * Returns true if the current classloader is default classloader,
       * otherwise false.
       */
      public function isDefaultClassLoader():Boolean {
         if (_classLoader == null) {
            return true;
         }

         var loaderType:String = getQualifiedClassName(classLoader);
         return (loaderType == getQualifiedClassName(DefaultClassLoader));
      }
   }
}